<template>
  <view :style="{'height': height === 'auto' ? elHeight : height, padding: height === 'auto' ? '10px 0' : '0'}" class="center">
    <view class="sh-slider"  >
      <view class="rail" :style="{
        'border-radius': railRadius,
        height: railHeight
      }">
      <!-- 第三段 -->
      <view class="rail3" :style="{opacity, width: `${width}px`, background: rail_3_color, 'border-radius': `${railRadius}` }"></view>
      <!-- 第二段 -->
      <view class="rail2" v-if="range" :style="{opacity, width: `${rightTranslateX - leftTranslateX + rWidth - 2 }px`, left: `${leftTranslateX}px`, 'border-radius': `${railRadius}`, background: rail_2_color}"></view>
      <view class="rail2" v-else :style="{opacity, width: `${rightTranslateX + rWidth - 2 }px`, 'border-radius': `${railRadius}`, background: rail_2_color}"></view>
      <!-- 第一段 -->
      <view class="rail1" style="z-index: 8;" :style="{opacity, width: `${leftTranslateX + lWidth - 2}px`, 'border-radius': `${railRadius}`, background: rail_1_color }" v-if="range"></view>
    </view>
      <view
        v-if="range"
        class="sh-block-left"
        :style="{opacity, 'transform': `translate(${leftTranslateX}px, -50%)` }"
        @touchend.stop.prevent="leftTouchcancel"
        @touchmove.stop.prevent="(e) => _moveLeftHandle(e)">
        <slot v-if="$slots.left" name="left"/>
        <view
          v-else
          class="block"
          :style="{
            width: sliderWidth,
            height: `calc(${sliderHeight} + ${_sliderPlusHeight})`,
            'border-radius': sliderRadius,
            transform: `scale(${annularScale})`,
            'box-shadow': _shadow(slider_bgc_1),
            background: slider_bgc_1
          }">
        </view>
      </view>
      <view
        class="sh-block-right"
        :style="{opacity, 'transform': `translate(${rightTranslateX}px, -50%)` }"
        @touchend.stop.prevent="(e) => rightTouchcancel(e)"
        @touchmove.stop.prevent="(e) => _moveRightHandle(e)">
        <slot v-if="$slots.right" name="right"/>
        <view
          v-else
          class="block"
          :style="{
            width: sliderWidth,
            height: `calc(${sliderHeight} + ${_sliderPlusHeight})`,
            'border-radius': sliderRadius,
            transform: `scale(${annularScale})`,
            'box-shadow': _shadow(slider_bgc_2),
            background: slider_bgc_2
          }">
        </view>
      </view>
    </view>
  </view>
</template>
<script>
export default {
  props: {
    min: {
      type: Number,
      default: 0
    },
    max: {
      type: Number,
      default: 100
    },
    silky: { // 丝滑模式
      type: Boolean,
      default: false
    },
    type: { // annular - 环形  circle - 普通
      type: String,
      default: 'circle'
    },
    disabled: { // 禁用
      type: Boolean,
      default: false
    },
    step: {
      type: Number,
      default: 1
    },
    height: {
      type: String,
      default: 'auto'
    },
    modelValue: {
      type: [Number, Array],
      default: 0
    },
    value: {
      type: [Number, Array],
      default: 0
    },
    railRadius: { // 滑轨圆角
      type: String,
      default: '0px'
    },
    railHeight: { // 滑轨高度
      type: String,
      default: '4px'
    },
    railColor: { // 滑轨颜色
      type: [Array, String],
      default: '#4b7bec'
    },
    sliderColor: { // 滑块颜色
      type: [Array, String],
      default: '#4b7bec'
    },
    sliderWidth: { // 滑块宽度
      type: String,
      default: '20px'
    },
    sliderHeight: { // 滑块高度
      type: String,
      default: '20px'
    },
    sliderRadius: { // 滑块圆角
      type: String,
      default: '20px'
    },
    sliderShadow: { // 滑块阴影
      type: String,
      default: ''
    },
  },
  emits: ['update:modelValue', 'input', 'change'],
  data() {
    return {
      leftTranslateX: 0,
      rightTranslateX: 0,
      mLeft: 0, // 最左边的距离
      width: 0,
      lWidth: 0,
      rWidth: 0,
      _sliderPlusHeight: '0px',
      elHeight: '0px',
      rightTouchcancel: () => {},
      leftTouchcancel: () => {},
      testStyle: {
        width: '30rpx',
        height: '30rpx',
        background: '#fff',
      }
    }
  },
  mounted() {
    this._getEL()
  },
  computed: {
    opacity() {
      return this.disabled ? 0.6 : 1
    },
    annularScale() {
      if (this.type === 'circle') return 1
      // 12 为圆环半径6 * 2
      return this.rWidth / (this.rWidth + 12)
    },
    _sliderShadow() {
      if (this.type === 'annular') {
        const shadow = this.sliderShadow.split(' ')
        const px  = shadow[3]
        if (!px) return this.sliderShadow
        let pxInit = Number(px.replace(/(rpx|px)/g, ''))
        if (isNaN(pxInit)) return this.sliderShadow
        if (this.annularScale !== 0) {
          // 阴影放大
          pxInit = pxInit / this.annularScale
        }
        shadow[3] = `calc(${pxInit}px + 6px)`
        return shadow.join(' ')
      }
      return this.sliderShadow
    },
    _min() {
      const max = this.max, min = this.min
      if (min > max) {
        return max
      }
      return min
    },
    _max() {
      let max = this.max, min = this._min
      if (this.min > max) {
        max = this.min
      }
      if (min === max) {
        max += 2
      }
      return max - min
    },
    _step() { // 每一步是多少值
      if (this._max % this.step !== 0) {
        return 1
      }
      if (this.step !== 0) {
        return this.step
      }
      return 1
    },
    _steps() {
      if (this._max % this._step === 0) {
        return (this._max / this._step) - Number(this.range)
      }
      return 0
    },
    _stepWidth() {
      return (this.width - this.rWidth - this.lWidth) / this._steps
    },
    _value() {
      const value = this.modelValue || this.value
      if (Array.isArray(value)) {
        let [start, end] = value
        start -= this._min
        end -= this._min
        if (value.length === 0) {
          return [0, this._max]
        }
        if (isNaN(Number(start)) || isNaN(Number(end))) {
          return [0, this._max]
        }
        if (Math.max(start, end) < 0 || Math.min(start, end) > this._max) {
          return [0, this._max]
        }
        if (end > this._max) {
          return [start, this._max]
        }
        if (start < 0 || start === end) {
          return [0, end]
        }
        if (Math.max(start, end) === start) {
          return [end, start]
        }
        return [start, end]
      }
      if (isNaN(Number(value))) {
        return 0
      }
      if (value > this._max || value < this._min) {
        return 0
      }
      return value - this._min
    },
    slider_bgc_1() {
      let color = ''
      if (!Array.isArray(this.sliderColor)) {
        color =  this.sliderColor
      } else {
        color = this.sliderColor[0]
      }
      return color
    },
    slider_bgc_2() {
      let color = ''
      if (!Array.isArray(this.sliderColor)) {
        color = this.sliderColor
      } else {
        if (this.sliderColor.length <= 1) {
          color = this.sliderColor[0]
        }
        if (this.sliderColor.length >= 2) {
          color = this.sliderColor[1]
        }
      }
      return color
    },
    rail_1_color() {
      if (!Array.isArray(this.railColor) || this.railColor.length === 1) {
        return '#f2f2f2'
      }
      if (this.railColor.length === 2) {
        return this.railColor[1]
      }
      return this.railColor[0]
    },
    rail_2_color() {
      if (!Array.isArray(this.railColor)) {
        return this.railColor
      }
      if (this.railColor.length === 1 || this.railColor.length === 2) {
        return this.railColor[0]
      }
      return this.railColor[1]
    },
    rail_3_color() {
      if (!Array.isArray(this.railColor) || this.railColor.length === 1) {
        return '#f2f2f2'
      }
      if (this.railColor.length === 2) {
        return this.railColor[1]
      }
      return this.railColor[2]
    },
    range() {
      const isArray = Array.isArray(this._value)
      return isArray
    },
  },
  methods: {
    _shadow(color) {
      if (this.type === 'circle') {
        return this._sliderShadow
      }
      return `0 0 0 4px #fff, 0 0 0 6px ${color}` + (this._sliderShadow ? `,${this._sliderShadow}` : '')
    },
    _getClientRect(className) {
      const _this = this
      return new Promise((resolve) => {
        uni.createSelectorQuery().in(_this).select(className).boundingClientRect(res => {
          resolve(res)
        }).exec()
      })
    },
    async _getEL() {
      const { left, width } = await this._getClientRect('.sh-slider')
      this.width = width
      this.mLeft = left
      const { width: rWidth, height: rHeight } = await this._getClientRect('.sh-block-right')
      this.rWidth = rWidth
      if (rHeight) {
        // 只有放大的元素才重新计算高度
        if (rWidth !== rHeight && this.annularScale !== 1)  {
          // 放大到相同的倍数 - 缩小后的高度 - 原本的高度
          this._sliderPlusHeight = (rHeight / this.annularScale) - (rHeight * this.annularScale) - rHeight + 'px'
        }
      }
      let stepWidth = (width - rWidth) / this._steps
      let lHeight = 0, rValue = this._value
      if (this.range) {
        rValue = this._value[1]
        const { width: lWidth, height } = await this._getClientRect('.sh-block-left')
        lHeight = height
        this.lWidth = lWidth
        stepWidth = (width - rWidth - lWidth) / this._steps
        // 初始化滑块位置
        this.leftTranslateX = this._value[0] / this._step * stepWidth
      }
      this.elHeight = Math.max(lHeight, rHeight) + 'px'
        // 初始化滑块位置 1 一个步长 如果是范围选择的话就要少一个步长
      this.rightTranslateX = ((rValue / this._step) - Number(this.range)) * stepWidth + this.lWidth
    },
    setValue(value) {
      if (value) {
        this._emit(value)
      }
      if (!this.range) {
        this.leftTranslateX = 0
        this.lWidth = 0
      }
      this._getEL()
    },
    _moveLeftHandle(e) {
      if (this.disabled) return
      const touch = e.changedTouches[0]
      let { clientX } = touch
      clientX -= this.mLeft + this.lWidth / 2 // 取中心点
      let minX = 0, maxX = this.rightTranslateX, value = 0, value1 = this._value[1]
      if (clientX < minX) {
        this._emit([value, value1])
        return this.leftTranslateX = minX
      }
      if (clientX + this.lWidth > maxX) {
        value = value1 - this._step
        this._emit([value, value1])
        this.leftTouchcancel = () => {}
        return this.leftTranslateX = maxX - this.rWidth
      }
      const step = Math.floor(clientX / this._stepWidth)
      value = step * this._step
      const leftTouchcancel = () => {
        this.leftTranslateX = step * this._stepWidth
      }
      if (this.silky) {
        this.leftTranslateX = clientX
        this.leftTouchcancel = leftTouchcancel
      } else {
        leftTouchcancel()
      }
      this._emit([value, value1])
    },
    _moveRightHandle(e) {
      if (this.disabled) return
      const touch = e.changedTouches[0]
      let { clientX } = touch
      clientX -= this.mLeft + this.rWidth / 2  // 取中心点
      let minX = this.leftTranslateX + this.lWidth , maxX = this.width - this.rWidth // 40 - 自身
      let value = 0, value1 = this._value[0] || this.value
      const minParse = Math.floor(value1 / this._step) * this._step + ((value1 % this._step) === 0 ? 0 : this._step)
      if (clientX <= minX) {
        if (this.range) {
          value = (Math.floor(value1 / this._step) + 1) * this._step
          this._emit([value1, value])
        } else {
          this._emit(value)
        }
        return this.rightTranslateX = minX
      }
      if (clientX > maxX) {
        if (this.range) {
          this._emit([value1, this._max])
        } else {
          this._emit(this._max)
        }
        this.rightTouchcancel = () => {}
        return this.rightTranslateX = maxX
      }
      const step = Math.floor((clientX - this.lWidth) / this._stepWidth)
      value = (step + Number(this.range)) * this._step
      let touchcancel = () => {
        if (minParse === value && this.range) {
          this.rightTranslateX = minX
          return
        }
        this.rightTranslateX = step * this._stepWidth + this.lWidth
      }
      if (this.silky) {
        this.rightTranslateX = clientX
        this.rightTouchcancel = touchcancel
      } else {
        touchcancel()
      }
      if (this.range) {
        this._emit([value1, value])
      } else {
        this._emit(value)
      }
    },
    _emit(value) {
      let isChange = false
      if (Array.isArray(value)) {
        const before = this._value[0] + this._value[1]
        let [start, end] = value
        const currnet = start + end
        isChange = before !== currnet
        value = [start + this._min, end + this._min]
      } else {
        isChange = this._value !== value
        value += this._min
      }
      if (!isChange) return
      this.$emit('change', value)
      this.$emit('update:modelValue', value)
      this.$emit('input', value)
    }
  }
}
</script>
<style lang="scss" scope>
.center {
  display: flex;
  align-items: center;
}
.sh-slider {
  position: relative;
  transition: all 0.1s;
  width: 100%;
  .rail {
    width: 100%;
    position: absolute;
    display: flex;
    left: 0;
    top: 0;
    transform: translateY(-50%);
    .rail1, .rail2, .rail3 {
      height: 100%;
      position: absolute;
      left: 0;
    }
  }
  .block {
    width: 20px;
    height: 20px;
    border-radius: 100px;
  }
  .sh-block-left,.sh-block-right {
    position: absolute;
    left: 0;
  }
  .silky {
    transition: all 0.2s;
  }
}
</style>
